# --*-- coding: utf-8 --*--

"""
Author: Darker
Description:
Version: 0.0.1
LastUpdateDate: 2021-07-13
UpdateURL:
LOG:
"""

from typing import Callable, Tuple
import pandas
from collections import OrderedDict
import inspect
import functools


class DataDriver(object):
	@staticmethod
	def run_data_from_excel(path: str, sheet_name: int = 0, header: int = 0, field_on_column: bool = False) -> Callable:
		"""
		说明：
			测试数据驱动测试案例
		示例：
			例如Excel中sheet页存在如下4行3列的数据，第1行通常为字段名，数据为3行3列（默认）。
				字段1	    字段2	    字段3
				字段1_数值1	字段2_数值1	字段3_数值1
				字段1_数值2	字段2_数值2	字段3_数值2
				字段1_数值3	字段2_数值3	字段3_数值3
			或  sheet页中第一列为字段名 其他列为数据 （指定参数field_on_column=True）
				字段1 字段1_数值1 字段1_数值2 字段1_数值3
				字段2 字段2_数值1 字段2_数值2 字段2_数值3
				字段3 字段3_数值1 字段3_数值2 字段3_数值3

			我们在编写某个自动化案例场景时，如果测试步骤相同只是测试数据不同的话，可以像这样编写：
			class Test(unittest.TestCase):
				...
				@run_data_from_excel(path=r".\Data\data.xlsx")
				def test_func(self, 字段1, 字段2, 字段3):
					...
					# 测试执行
					...
				...
			在编写定义test_func时，参数名要与Excel中的字段名一致

		:param path: Excel路径
		:param sheet_name: sheet页编号或名称
		:param header: 默认为0 不使用第一行数据(当第一行为字段名时) header=None 解析第一行数据(没有字段名时)
		:param field_on_column:
		"""
		if not field_on_column:
			excel_data = pandas.read_excel(io=path, sheet_name=sheet_name, header=header)
			# OrderedDict可以将一个Series转换为key-value，其中key为Series的index
			kwargs = (OrderedDict(excel_data.iloc[row]) for row in range(excel_data.shape[0]))
		# print(list(kwargs))
		else:
			excel_data = pandas.read_excel(io=path, sheet_name=sheet_name, header=None, index_col=0)
			kwargs = (OrderedDict(excel_data[column + 1]) for column in range(excel_data.shape[1]))

		def decorator(func):
			return DataDriver._return_wrapper(func=func, kwargs=kwargs)

		return decorator

	@staticmethod
	def run_date_from_csv(path: str, sep: str = ",", header: int = 0, field_on_column: bool = False) -> Callable:
		"""
		说明：
			测试数据驱动测试案例
		示例：
			例如Csv中存在如下4行3列的数据，第1行通常为字段名，数据为3行3列（默认）。
				字段1	  ,字段2	     ,字段3
				字段1_数值1,字段2_数值1,字段3_数值1
				字段1_数值2,字段2_数值2,字段3_数值2
				字段1_数值3,字段2_数值3,字段3_数值3
			或  Csv中第一列为字段名，其他列为数值（指定参数field_on_column=True）
				字段1,字段1_数值1,字段1_数值2,字段1_数值3
				字段2,字段2_数值1,字段2_数值2,字段2_数值3
				字段3,字段3_数值1,字段3_数值2,字段3_数值3

			我们在编写某个自动化案例场景时，如果测试步骤相同只是测试数据不同的话，可以像这样编写：
			class Test(unittest.TestCase):
				...
				@run_date_from_csv(path=r".\Data\data.csv")
				def test_func(self, 字段1, 字段2, 字段3):
					...
					# 测试执行
					...
				...
			在编写定义test_func时，参数个数与Csv中的列名个数要一致

		:param path: Csv路径
		:param sep: Csv数据分隔符，默认为“,”
		:param header: 默认为0 不使用第一行数据(当第一行为字段名时) header=None 解析第一行数据(没有字段名时)
		:param field_on_column:
		"""
		if not field_on_column:
			csv_data = pandas.read_csv(filepath_or_buffer=path, sep=sep, header=header)
			kwargs = (OrderedDict(csv_data.iloc[row]) for row in range(csv_data.shape[0]))
		else:
			csv_data = pandas.read_csv(filepath_or_buffer=path, sep=sep, header=None, index_col=0)
			kwargs = (OrderedDict(csv_data[column + 1]) for column in range(csv_data.shape[1]))

		def decorator(func):
			return DataDriver._return_wrapper(func=func, kwargs=kwargs)

		return decorator

	@staticmethod
	def _return_wrapper(func: Callable, kwargs: Tuple[OrderedDict]) -> Callable:
		"""
		说明：
			返回被装饰的函数wrapper
		:param func: 被装饰函数func
		:param kwargs: 读取文件中的数据  such as ({p1: v1, p2: v2}, {p1 : v3, p2: v4} ... )
		:return:
		"""
		# zz: 2018年11月7日 根据func定义的参数来匹配数据文件的字段名，只传入匹配上的列数据
		# zz: 2018年11月9日 将上面公共代码提取到这里
		co_var_names = tuple(inspect.signature(func).parameters)  # 获取func函数的形参
		if 'self' in co_var_names:
			_co_var_names = list(co_var_names)
			# print(_co_var_names)
			_co_var_names.remove('self')
			co_var_names = tuple(_co_var_names)
			self_exist = True
		else:
			self_exist = False
		nkwargs = ({key: kw[key] for key in co_var_names} for kw in kwargs)
		if self_exist:
			@functools.wraps(func)
			def warpper(self):  # 被装饰的方法定义在类中，被调用是通过 对象.func() or self.func()调用，因此这里需要加上self参数
				for nkw in nkwargs:
					func(self, **nkw)

			return warpper
		else:
			@functools.wraps(func)
			def warpper():
				for nkw in nkwargs:
					func(**nkw)

			return warpper
